<?php

class Utility_SortFunctions {

    const TYPE_STRING = 'string';
    const TYPE_NUMBER = 'number';
    const TYPE_DATE   = 'date';
    const TYPE_RANK   = 'rank';

    const PERSISTENT_MOVE_RIGHT =  2;
    const MOVE_RIGHT            =  1;
    const MOVE_LEFT             = -1;
    const PERSISTENT_MOVE_LEFT  = -2;

    private $arr_sort_keys = array ();

    static public function sort(&$arr_data, $mix_sort) {
        
        $arr_keys = self::standardise_sort_meta($mix_sort);
        
        if (! is_null($arr_keys)) {
            $obj = new self();
            $obj->sort_by($arr_data, $arr_keys);
        }
    }
    
    static public function standardise_sort_meta($mix_sort_meta) {
        
        $arr_sort_meta = null;
        if(is_string($mix_sort_meta)) {
            $arr_sort_meta = array (
                $mix_sort_meta => array (
                    'type'      => self::TYPE_STRING,
                    'direction' => SORT_ASC
                )
            );
        } else if(is_array($mix_sort_meta)) {
        
            $arr_sort_meta = array();
            foreach($mix_sort_meta as $str_sort_key => $mix_sort_meta) {
    
                $enm_type = self::TYPE_STRING;
                $enm_direction = SORT_ASC;
    
                if(is_array($mix_sort_meta)) {
                    if(isset($mix_sort_meta['direction'])) {
                        $enm_direction = self::standardise_direction($mix_sort_meta['direction']);
                    }
                    if(isset($mix_sort_meta['type'])) {
                        $enm_type = self::standardise_type($mix_sort_meta['type']);
                    }
                } else {
                    
                    switch($mix_sort_meta) {
                        case self::TYPE_RANK:
                        case self::TYPE_DATE:
                        case self::TYPE_STRING:
                            $enm_type = $mix_sort_meta;
                            break;
                        case self::TYPE_NUMBER:
                            $enm_type = $mix_sort_meta;
                            $enm_direction = SORT_DESC;
                            break;
                        default:
                            $enm_direction = self::standardise_direction($mix_sort_meta);
                            break;
                    }
                    
                }
    
                $arr_sort_meta[$str_sort_key] = array(
                    'type' => $enm_type,
                    'direction' => $enm_direction
                );
            }
            
        }
        
        return $arr_sort_meta;
        
    }

    static public function standardise_type($str) {

        $str_type = self::TYPE_STRING;

        $str = strtolower($str);

        switch($str) {
        case 'integer':
        case 'float':
        case self::TYPE_NUMBER:
            $str_type = self::TYPE_NUMBER;
            break;
        case self::TYPE_RANK:
            $str_type = self::TYPE_RANK;
            break;
        case self::TYPE_DATE:
            $str_type = self::TYPE_DATE;
            break;
        case self::TYPE_STRING:
        default:
            $str_type = self::TYPE_STRING;
            break;
        }

        return $str_type;
    }

    static public function standardise_direction($mix) {

        $enm_direction = SORT_ASC;

        $str = strtolower($mix);
        switch($str) {
            case 'asc':
            case '1':
            case SORT_ASC:
                $enm_direction = SORT_ASC;
                break;
            case 'desc':
            case '-1':
            case SORT_DESC:
                $enm_direction = SORT_DESC;
                break;
        }

        return $enm_direction;

    }

    protected function set_sort_keys($arr_keys) {

        $arr_keys_standardised = array();
        foreach($arr_keys as $str_sort_key => $mix_sort_meta) {

            $enm_type = self::TYPE_STRING;
            $enm_direction = SORT_ASC;

            if(is_array($mix_sort_meta)) {
                if(isset($mix_sort_meta['direction'])) {
                    $enm_direction = self::standardise_direction($mix_sort_meta['direction']);
                }
                if(isset($mix_sort_meta['type'])) {
                    $enm_type = self::standardise_type($mix_sort_meta['type']);
                }
            } else {
                
                switch($mix_sort_meta) {
                    case self::TYPE_RANK:
                    case self::TYPE_DATE:
                    case self::TYPE_STRING:
                        $enm_type = $mix_sort_meta;
                    case self::TYPE_NUMBER:
                        $enm_type = $mix_sort_meta;
                        $enm_direction = SORT_DESC;
                        break;
                    default:
                        $enm_direction = self::standardise_direction($mix_sort_meta);
                        break;
                }
                
            }

            $arr_keys_standardised[$str_sort_key] = array(
                'type' => $enm_type,
                'direction' => $enm_direction
            );
        }

        $this->arr_sort_keys = $arr_keys_standardised;
    }

    public function sort_by(&$arr_data, $arr_keys) {

        if(is_array($arr_data) && is_array($arr_keys)) {
            $this->set_sort_keys($arr_keys);
            uasort($arr_data, array($this, 'sort_worker'));
        }

    }

    protected function get_data_type($str_key) {

        $str_type = self::TYPE_STRING;

        if(isset($this->arr_sort_keys[$str_key])) {
            $str_type = $this->arr_sort_keys[$str_key]['type'];
        }

        return $str_type;
    }

    protected function sort_worker($a, $b) {

        foreach($this->arr_sort_keys as $str_key => $arr_sort_meta) {

            $enm_type = $arr_sort_meta['type'];
            $enm_direction = $arr_sort_meta['direction'];

            $mix_a = isset($a[$str_key]) ? $a[$str_key] : null;
            $mix_b = isset($b[$str_key]) ? $b[$str_key] : null;

            if($mix_a !== $mix_b) {

                $int_return = $this->compare($enm_type, $mix_a, $mix_b);
                if($enm_direction === SORT_DESC) {
                    if($int_return == self::PERSISTENT_MOVE_RIGHT) {
                        $int_return = self::MOVE_RIGHT;
                    } else if($int_return == self::PERSISTENT_MOVE_LEFT) {
                        $int_return = self::MOVE_LEFT;
                    } else {
                        // change the sign of the return type,
                        // because it's descending
                        $int_return *= -1;
                    }
                } else {
                    if($int_return == self::PERSISTENT_MOVE_RIGHT) {
                        $int_return = self::MOVE_RIGHT;
                    } else if($int_return == self::PERSISTENT_MOVE_LEFT) {
                        $int_return = self::MOVE_LEFT;
                    }
                }

                return $int_return;
            }

        }

    }

    protected function compare($enm_type, $mix_a, $mix_b) {

        $enm_out = 0;

        // if (a) is null, then return a PERSISTENT_MOVE_RIGHT, to ensure
        // the value is at the end of the value list
        if(is_null($mix_a)) {
            return self::PERSISTENT_MOVE_RIGHT;
        }

        // if (b) is null, then return a PERSISTENT_MOVE_LEFT, to ensure
        // the value is at the end of the value list
        else if(is_null($mix_b)) {
            return self::PERSISTENT_MOVE_LEFT;
        }

        switch($enm_type) {
            case self::TYPE_NUMBER:
                $enm_out = $this->compare_number($mix_a, $mix_b);
                break;
            case self::TYPE_RANK:
                $enm_out = $this->compare_rank($mix_a, $mix_b);
                break;
            case self::TYPE_DATE:
                $enm_out = $this->compare_date($mix_a, $mix_b);
                break;
            case self::TYPE_STRING:
            default:
                $enm_out = $this->compare_string($mix_a, $mix_b);
                break;
        }

        return $enm_out;

    }

    protected function compare_number($mix_a, $mix_b) {
        return ($mix_a < $mix_b) ? self::MOVE_LEFT : self::MOVE_RIGHT;
    }

    protected function compare_string($mix_a, $mix_b) {
        return strcasecmp($mix_a, $mix_b);
    }

    protected function compare_date($mix_a, $mix_b) {

        $int_a = !is_null($mix_a) ? Utility_DateFunctions::toInt($mix_a) : null;
        $int_b = !is_null($mix_b) ? Utility_DateFunctions::toInt($mix_b) : null;

        return ($int_a < $int_b) ? self::MOVE_LEFT : self::MOVE_RIGHT;

    }

    protected function compare_rank($mix_a, $mix_b) {
        return ($mix_a < $mix_b) ? self::MOVE_LEFT : self::MOVE_RIGHT;
    }

}
